#pragma once
#include <iostream>
#include <functional>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "log.hpp"

using func_t = std::function<std::string(const std::string&)>;
using namespace MyLog;
class UDPsocket
{
public:
    UDPsocket(u_int16_t port, func_t func)
        : _port(port),
          _isrunning(false),
          _func(func)
    {
    }
    ~UDPsocket() {}

    void Init()
    {
        // 1.创建udp socket套接字
        int n = socket(AF_INET, SOCK_DGRAM, 0);
        if (n < 0)
        {
            LOG(loglevel::FATAL) << "create socket error";
            exit(1);
        }
        _sockfd = n;
        LOG(loglevel::INFO) << "create socket success, sockfd: " << _sockfd;

        /* Structure describing an Internet socket address.  */
        //struct sockaddr_in
        //{
        //    __SOCKADDR_COMMON(sin_);
        //    in_port_t sin_port;      /* Port number.  */
        //    struct in_addr sin_addr; /* Internet address.  */

        //    /* Pad to size of `struct sockaddr'.  */
        //    unsigned char sin_zero[sizeof(struct sockaddr) - __SOCKADDR_COMMON_SIZE - sizeof(in_port_t) - sizeof(struct in_addr)];
        //};

        // 2.bind socket
        // 绑定socket时，需要传入sockadr_in，将其强转为sockaddr
        // sockaddr_in有三个参数需要填写
        // 参数1：
        // 地址家族，因为socket既有网络通信，也有本地通信，所以要用这个地址家族来区分到底是什么通信。
        // AF_INET:表示网络通信    AF_UNIX：表示本地通信
        // 参数2：
        // 端口号，因为网络通信本质上就是进程在通信，而端口号在一个主机内标识了进程的唯一性。
        // 所以，我们在绑定的时候，得知道是那个进程准备要进行通信
        // 参数3：
        // IP地址，在网络通信中，一般不将一个socket绑定到一个固定的ip地址上
        // 如果绑定了，就会导致该服务端只能收到来自同一个ip地址发出的报文
        // 所以，我们在绑定的时候一般都绑INADDR_ANY，其实就是0号地址, 这样该服务器就可以收到来自任何ip地址对指定端口发出的所有报文

        // 但是，在网络中，数据的传输都是以大端传递的，所以我们要将端口号转化为大端存储再进行绑定
        struct sockaddr_in sd;
        bzero(&sd, sizeof(sd)); // 对结构体进行清空
        sd.sin_family = AF_INET;
        sd.sin_port = htons(_port);
        sd.sin_addr.s_addr = INADDR_ANY;
        n = bind(_sockfd, (sockaddr*)&sd, sizeof(sd));
        if (n < 0)
        {
            LOG(loglevel::FATAL) << "bind socket error";
            exit(2);
        }
        LOG(loglevel::INFO) << "bind socket success";
    }

    void Start()
    {
        _isrunning = true;
        while(true){
            // 作为服务端，我们会收到来自客户端的报文，所以当没有报文的时候，我们要阻塞等待报文
            // recvfrom在获取报文的时候，要进行阻塞等待即第4个参数，设置为0
            // 报文我们得直到是谁发的，所以我们依旧得使用sockaddr_in来作为输出型参数获取发送报文的socket信息
            char buffer[1024];
            struct sockaddr_in who;
            bzero(&who, sizeof(who));
            socklen_t who_size = sizeof(who); // socklen_t 本质上就是一个unsigned int
            int n = recvfrom(_sockfd, buffer, sizeof(buffer), 0, (struct sockaddr*)&who, &who_size);
            if(n > 0) {
                buffer[n] = 0;
                LOG(loglevel::INFO) << "cilent say->" << "[" << inet_ntoa(who.sin_addr) << "]" << "[" << ntohs(who.sin_port) << "] " << buffer;
                // 这里说明服务端获取到了客户端发送的报文
                // 我们这里将报文编辑之后，重新发送回客户端，表示服务端对客户端的报文做出了处理
                // 发送报文时，我们处理常规消息，我们还得知道要发送给谁，所以还得知道发送的目的ip地址和端口

                // 使用回调方法，处理客户端发送的请求
                // 将处理结果返回给客户端
                std::string result = _func(buffer);

                int n = sendto(_sockfd, result.c_str(), result.size(), 0, (struct sockaddr*)&who, sizeof(who));
                (void)n;
            }
        }
    }

private:
    u_int16_t _port; // 端口号
    int _sockfd;     // 标识socket的文件描述符
    bool _isrunning;
    func_t _func;    // 回调方法
};